
// nodejs Version = 0.10.21
// sudo npm install -g nodemon
// sudo npm install -g forever
// npm install xml2js
// npm install express@"<4.0.0"
// npm install socket.io

var moduleNet = require('net');
var moduleFs = require('fs');
var moduleXml2js = require('xml2js');
var moduleHttp  = require('http');
var moduleExpress = require('express');
var moduleSocketIo = require('socket.io');

var startDate = new Date();
console.log('');console.log('');console.log('');
console.log('+++ CubeServer Start +++');
console.log(startDate.toLocaleString());

var accountFilename = '/cubeaccount-snow.xml';
var accountData = null;
moduleFs.readFile(__dirname + accountFilename, function(err, data) {
	var parser = new moduleXml2js.Parser();
	parser.parseString(data, function (err, result) {
		accountData = result.Account;
		
		for (var i=0; i<accountData.User.length; i++) {
			accountData.User[i].id = accountData.User[i].id[0];
			accountData.User[i].pwd = accountData.User[i].pwd[0];
			if (accountData.User[i].expireDate) {
				accountData.User[i].expireDate = new Date(accountData.User[i].expireDate[0]);
			}
			
			accountData.User[i]._islogin = false;
			accountData.User[i]._allowReconnection = false;
		}

		var padUser = {};
		padUser.id = 'cuberemote-pad';
		padUser.pwd = 'cubecontrol';
		padUser._islogin = false;
		padUser._allowReconnection = true;
		accountData.User.push(padUser);

		console.log(accountData);
	});
});

var layoutFilename = '/cubelayout-snow.xml';
var layoutData = null;
moduleFs.readFile(__dirname + layoutFilename, function(err, data) {
	var parser = new moduleXml2js.Parser();
	parser.parseString(data, function (err, result) {
		layoutData = result.Layout;

		layoutData.cx = layoutData.cx[0];
		layoutData.cy = layoutData.cy[0];
		layoutData.row = layoutData.row[0];
		layoutData.column = layoutData.column[0];
		for (var i=0; i<layoutData.Button.length; i++) {
			// array -> value
			layoutData.Button[i].cid = layoutData.Button[i].cid[0];
			layoutData.Button[i].bid = layoutData.Button[i].bid[0];
			layoutData.Button[i].rid = layoutData.Button[i].rid[0];
			layoutData.Button[i].name = layoutData.Button[i].name[0];
			layoutData.Button[i].notUsed = layoutData.Button[i].notUsed[0];
			layoutData.Button[i].pos = layoutData.Button[i].pos[0];
			layoutData.Button[i].xycxcy = layoutData.Button[i].xycxcy[0];

			layoutData.Button[i].rtype = 0;		// 0(relay), xy1(x:flagRemote, y:isHeating)
			layoutData.Button[i].status = 64;	// 0x40(unknown)
		}
		console.log(layoutData);
	});
});

//=============================================================================
// Control Server
//=============================================================================
var controlServer = moduleNet.createServer();
var controlServerPort = 58060;
var controlClients = new Array();
controlServer.on('connection', function (client) {

	controlClients.push(client);
	client._addr = client.remoteAddress + ':' + client.remotePort;
	console.log('controlServer new connection :', controlClients.length, client._addr);

	client.on('data', function (data) {
		var strPacket = data.toString();
		var curIdx = 1, findIdx;
		var cmdPacket = "";

		if (('$' != strPacket.charAt(0)) || ('$' != strPacket.charAt(strPacket.length - 1))) {
			console.log('controlServer packet error :', strPacket);
			return false;
		}
		console.log('controlServer packet :', strPacket);

		findIdx = strPacket.indexOf(':', curIdx);
		if (0 > findIdx) {
			return false;
		}
		cmdPacket = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		if ('connect' == cmdPacket) {
			parseConnect(client, strPacket, curIdx);
		} else if ('update' == cmdPacket) {
			parseUpdate(client, strPacket, curIdx);
		} else if ('reboot' == cmdPacket) {
			parseReboot(strPacket);
		} else if ('getfile' == cmdPacket) {
			parseGetFile(client, strPacket, curIdx);
		} else if ('sendfile' == cmdPacket) {
			parseSendFile(client, strPacket, curIdx);
		} else if ('temperature' == cmdPacket) {
			parseTemperature(strPacket);
		}
	});
	client.on('end', function () {
		console.log('controlServer client end:', client._addr);
	});
	client.on('timeout', function () {
		console.log('controlServer client timeout:', client._addr);
	});
	client.on('drain', function () {
		console.log('controlServer client drain:', client._addr);
	});
	client.on('error', function () {
		console.log('controlServer client error:', client._addr);
	});
	client.on('close', function () {
		// end -> close or error -> close
		parseDisconnect(client);

		controlClients.splice(controlClients.indexOf(client), 1);
		console.log('controlServer client close:', controlClients.length, client._addr);
	});
});
controlServer.on('error', function (error) {
	console.log('controlServer error:', error.message);
});
controlServer.on('close', function () {
	console.log('controlServer close');
});
controlServer.listen(controlServerPort);
console.log('controlServer running at port ' + controlServerPort);
//=============================================================================

//=============================================================================
// Remote Server
//=============================================================================
var remoteServer = moduleNet.createServer();
var remoteServerPort = 58070;
var remoteClients = new Array();
remoteServer.on('connection', function (client) {

	remoteClients.push(client);
	client._addr = client.remoteAddress + ':' + client.remotePort;
	console.log('remoteServer new connection :', remoteClients.length, client._addr);

	client.on('data', function (data) {
		var strPacket = data.toString();
		var curIdx = 1, findIdx;
		var cmdPacket = "";

		if (('$' != strPacket.charAt(0)) || ('$' != strPacket.charAt(strPacket.length - 1))) {
			console.log('remoteServer packet error :', strPacket);
			return false;
		}
		console.log('remoteServer packet :', strPacket);

		findIdx = strPacket.indexOf(':', curIdx);
		if (0 > findIdx) {
			return false;
		}
		cmdPacket = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		if ('login' == cmdPacket) {
			parseLogin(client, strPacket, curIdx);
		} else if ('set' == cmdPacket) {
			parseSet(strPacket);
		} else if ('sync' == cmdPacket) {
			parseSync(strPacket);
		} else if ('reboot' == cmdPacket) {
			parseReboot(strPacket);
		} else if ('getfile' == cmdPacket) {
			parseGetFile(client, strPacket, curIdx);
		} else if ('sendfile' == cmdPacket) {
			parseSendFile(client, strPacket, curIdx);
		} else if ('option' == cmdPacket) {
			parseOption(strPacket);
		}
	});
	client.on('end', function () {
		console.log('remoteServer client end:', client._addr);
	});
	client.on('timeout', function () {
		console.log('remoteServer client timeout:', client._addr);
	});
	client.on('drain', function () {
		console.log('remoteServer client drain:', client._addr);
	});
	client.on('error', function () {
		console.log('remoteServer client error:', client._addr);
	});
	client.on('close', function () {
		// end -> close or error -> close

		if (accountData) {
			if (accountData.User.length > client._accountIndex) {
				if (true == accountData.User[client._accountIndex]._islogin) {
					accountData.User[client._accountIndex]._islogin = false;
				}
			}
		}

		remoteClients.splice(remoteClients.indexOf(client), 1);
		console.log('remoteServer client close:', remoteClients.length, client._addr);
	});
});
remoteServer.on('error', function (error) {
	console.log('remoteServer error:', error.message);
});
remoteServer.on('close', function () {
	console.log('remoteServer close');
});
remoteServer.listen(remoteServerPort);
console.log('remoteServer running at port ' + remoteServerPort);
//=============================================================================

//=============================================================================
// Http Server
//=============================================================================
var httpApp = moduleExpress();
var httpServer = moduleHttp.createServer(httpApp);
var httpServerPort = 58080;
var httpIoServer = moduleSocketIo.listen(httpServer);
var httpClients = new Array();

httpApp.use(moduleExpress.static('http'));
httpApp.use(moduleExpress.bodyParser());
httpApp.use(httpApp.router);
httpApp.post('/login', function(req, res) {
	var id = req.param('uid');
	var pwd = req.param('pwd');

	if (accountData) {
		for (var i=0; i<accountData.User.length; i++) {
			if ((accountData.User[i].id == id) && (accountData.User[i].pwd == pwd)) {
				if (accountData.User[i].expireDate) {
					var nowDate = new Date();
					var elapsed = nowDate.getTime() - accountData.User[i].expireDate.getTime();
					if (0 < elapsed) {
						console.log('checkExpireDate :', accountData.User[i].expireDate, nowDate);
						res.redirect('back');
						return;
					}
				}

				if (false == accountData.User[i]._islogin) {
					//accountData.User[i]._islogin = true;
					console.log('httpServer login ok :', id, pwd);
					res.redirect('/control.html?id=' + id);
					return;
				} else {
					console.log('httpServer login error : 중복 로그인');
					res.redirect('back');
					return;
				}
			}
		}
	} else {
		console.log('httpServer accountData error :', id, pwd);
		res.redirect('back');
	}
});

httpIoServer.set('log level', 1);	// 0(error), 1(warn), 2(info), 3(debug, default)
httpIoServer.sockets.on('connection', function(client) {

	httpClients.push(client);
	console.log('httpServer new connection :', httpClients.length, controlClients.length);

	if (layoutData.Button.length) {
		client.emit('layout', makePacketLayout());
	} else {
		client.emit('nodevice', '');
	}

	client.on('layout', function (data) {
		console.log('    recv :', data);

		var findIdx = data.lastIndexOf('$');
		if (-1 < findIdx) {
			var id = data.substr(findIdx + 1);
			for (var i=0; i<accountData.User.length; i++) {
				if (accountData.User[i].id == id) {
					if (false == accountData.User[i]._islogin) {
						accountData.User[i]._islogin = true;
						client._accountIndex = i;
					}

					break;
				}
			}
		}
	});
	client.on('nodevice', function (data) {
		console.log('    recv :', data);
	});
	client.on('update', function (data) {
		console.log('    recv :', data);
	});
	client.on('set', function (data) {
		console.log('    recv :', data);
		parseSet(data);
	});
	client.on('option', function (data) {
		console.log('    recv :', data);
		parseOption(data);
	});
	client.on('disconnect', function (data) {
		if (accountData.User.length > client._accountIndex) {
			if (true == accountData.User[client._accountIndex]._islogin) {
				accountData.User[client._accountIndex]._islogin = false;
			}
		}

		httpClients.splice(httpClients.indexOf(client), 1);
		console.log('httpServer client end:', client.id, httpClients.length);
	});
});
httpServer.listen(httpServerPort, function() {
	console.log('httpServer running at port', httpServerPort);
});
//=============================================================================


//=============================================================================
function parseConnect(client, strPacket, curIdx) {
	var findIdx, firstIdx, lastIdx;

	findIdx = strPacket.indexOf(',', curIdx);
	client._cid = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	findIdx = strPacket.indexOf(',', curIdx);
	client._cname = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	client._Boards = new Array();

	firstIdx = strPacket.indexOf('[', curIdx);
	lastIdx = strPacket.indexOf(']', curIdx);
	while ((-1 < firstIdx) && (-1 < lastIdx)) {
		curIdx = firstIdx + 1;

		var board = {};

		findIdx = strPacket.indexOf(',', curIdx);
		board._bid = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		findIdx = strPacket.indexOf(',', curIdx);
		board._bname = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		board._Relays = new Array();

		firstIdx = strPacket.indexOf('<', curIdx);
		lastIdx = strPacket.indexOf('>', curIdx);
		while ((-1 < firstIdx) && (-1 < lastIdx)) {
			curIdx = firstIdx + 1;

			var relay = {};

			findIdx = strPacket.indexOf(',', curIdx);
			relay._rid = strPacket.substr(curIdx, findIdx-curIdx);
			curIdx = findIdx + 1;

			findIdx = strPacket.indexOf(',', curIdx);
			relay._rname = strPacket.substr(curIdx, findIdx-curIdx);
			curIdx = findIdx + 1;

			findIdx = strPacket.indexOf(',', curIdx);
			relay._notUsed = strPacket.substr(curIdx, findIdx-curIdx);
			curIdx = findIdx + 1;

			findIdx = strPacket.indexOf(',', curIdx);
			relay._rtype = strPacket.substr(curIdx, findIdx-curIdx);
			curIdx = findIdx + 1;

			findIdx = strPacket.indexOf(',', curIdx);
			relay._status = strPacket.substr(curIdx, findIdx-curIdx);
			curIdx = findIdx + 1;

			for (var i=0; i<layoutData.Button.length; i++) {
				if ((layoutData.Button[i].cid == client._cid)
					&& (layoutData.Button[i].bid == board._bid)
					&& (layoutData.Button[i].rid == relay._rid)) {
					layoutData.Button[i].name = relay._rname;
					layoutData.Button[i].notUsed = relay._notUsed;
					layoutData.Button[i].rtype = relay._rtype;
					layoutData.Button[i].status = relay._status;
					break;
				}
			}

			board._Relays.push(relay);

			firstIdx = strPacket.indexOf('<', curIdx);
			lastIdx = strPacket.indexOf('>', curIdx);

			var tmpIdx = strPacket.indexOf('[', curIdx);
			if ((-1 < tmpIdx) && (tmpIdx < firstIdx)) {
				break;
			}
		}
		curIdx = findIdx + 1;

		client._Boards.push(board);

		firstIdx = strPacket.indexOf('[', curIdx);
		lastIdx = strPacket.indexOf(']', curIdx);
	}

	var packetLayout = makePacketLayout();
	// qt-remote send layout
	for (var i=0; i<remoteClients.length; i++) {
		console.log('remoteServer send layout :', remoteClients[i]._addr);
		remoteClients[i].write(packetLayout);
	}
	// web-client send layout
	for (var i=0; i<httpClients.length; i++) {
		console.log('httpServer send layout :', httpClients[i]._addr);
		httpClients[i].emit('layout', packetLayout);
	}
}
//=============================================================================
function parseUpdate(client, strPacket, curIdx) {
	var findIdx, firstIdx, lastIdx;
	var cid, bid, rid, status;
	var layoutChanged = false;

	firstIdx = strPacket.indexOf('[', curIdx);
	lastIdx = strPacket.indexOf(']', curIdx);
	while ((-1 < firstIdx) && (-1 < lastIdx)) {
		curIdx = firstIdx + 1;

		findIdx = strPacket.indexOf(',', curIdx);
		cid = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;
		
		findIdx = strPacket.indexOf(',', curIdx);
		bid = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;
		
		findIdx = strPacket.indexOf(',', curIdx);
		rid = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		findIdx = strPacket.indexOf(',', curIdx);
		status = strPacket.substr(curIdx, findIdx-curIdx);
		curIdx = findIdx + 1;

		for (var c=0; c<controlClients.length; c++) {
			if (controlClients[c]._cid == cid) {
				for (var b=0; b<controlClients[c]._Boards.length; b++) {
					if (controlClients[c]._Boards[b]._bid == bid) {
						for (var r=0; r<controlClients[c]._Boards[b]._Relays.length; r++) {
							if (controlClients[c]._Boards[b]._Relays[r]._rid == rid) {
								controlClients[c]._Boards[b]._Relays[r]._status = status;
								//console.log('parseUpdate() client', cid, bid, rid, status);
								break;
							}
						}
						break;
					}
				}
				break;
			}
		}
		for (var i=0; i<layoutData.Button.length; i++) {
			if ((layoutData.Button[i].cid == cid)
				&& (layoutData.Button[i].bid == bid)
				&& (layoutData.Button[i].rid == rid)) {
				layoutData.Button[i].status = status;
				layoutChanged = true;
				//console.log('parseUpdate() layoutData', cid, bid, rid, status);
				break;
			}
		}

		firstIdx = strPacket.indexOf('[', curIdx);
		lastIdx = strPacket.indexOf(']', curIdx);
	}

	if (layoutChanged) {
		// qt-remote send update
		for (var i=0; i<remoteClients.length; i++) {
			//console.log('remoteServer send update :', i);
			remoteClients[i].write(strPacket);
		}
		// web-client send update
		for (var i=0; i<httpClients.length; i++) {
			//console.log('httpServer send update :', i);
			httpClients[i].emit('update', strPacket);
		}
	}
}
//=============================================================================
function parseReboot(strPacket) {
	// throw out controlServer
	for (var i=0; i<controlClients.length; i++) {
		controlClients[i].write(strPacket);
	}

	console.log('');console.log('');console.log('');
	setTimeout(function() {
		console.log('process.exit()');
		process.exit();
	}, 1000);
}
//=============================================================================
function parseDisconnect(client) {
	for (var b=0; b<client._Boards.length; b++) {
		for (var r=0; r<client._Boards[b]._Relays.length; r++) {
			for (var i=0; i<layoutData.Button.length; i++) {
				if ((layoutData.Button[i].cid == client._cid)
					&& (layoutData.Button[i].bid == client._Boards[b]._bid)
					&& (layoutData.Button[i].rid == client._Boards[b]._Relays[r]._rid)) {
					layoutData.Button[i].status = 64;	// 0x40(unknown)
					break;
				}
			}
		}
	}

	var packetLayout = makePacketLayout();
	// qt-remote send layout
	for (var i=0; i<remoteClients.length; i++) {
		console.log('remoteServer send layout :', remoteClients[i]._addr);
		remoteClients[i].write(packetLayout);
	}
	// web-client send layout
	for (var i=0; i<httpClients.length; i++) {
		console.log('httpServer send layout :', i);
		httpClients[i].emit('layout', packetLayout);
	}
}
//=============================================================================
function parseLogin(client, strPacket, curIdx) {
	var findIdx, id, pwd;

	findIdx = strPacket.indexOf(',', curIdx);
	id = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	findIdx = strPacket.indexOf(',', curIdx);
	pwd = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	if (accountData) {
		for (var i=0; i<accountData.User.length; i++) {
			if ((accountData.User[i].id == id) && (accountData.User[i].pwd == pwd)) {
				if (accountData.User[i].expireDate) {
					var nowDate = new Date();
					var elapsed = nowDate.getTime() - accountData.User[i].expireDate.getTime();
					if (0 < elapsed) {
						console.log('checkExpireDate :', accountData.User[i].expireDate, nowDate);
						client.destroy();	// ---> close
						return;
					}
				}

				if ((false == accountData.User[i]._islogin) || (true == accountData.User[i]._allowReconnection)) {
					accountData.User[i]._islogin = true;
					client._accountIndex = i;

					console.log('remoteServer login ok :', id, pwd);
					client.write(makePacketLayout());
					return;
				} else {
					console.log('remoteServer login error : 중복 로그인', id, pwd);
					client.write('$error:중복 로그인,$', 'utf8', function () {
						client.destroy();	// ---> close
					});
					return;
				}

				break;
			}
		}
	} else {
		console.log('remoteServer accountData error :', id, pwd);
		client.write('$error:계정 초기화 중,$', 'utf8', function () {
			client.destroy();	// ---> close
		});
	}
}
//=============================================================================
function parseSet(strPacket) {
	// throw out controlServer
	for (var i=0; i<controlClients.length; i++) {
		controlClients[i].write(strPacket);
	}
}
//=============================================================================
function parseSync(strPacket) {
	// throw out controlServer
	for (var i=0; i<controlClients.length; i++) {
		controlClients[i].write(strPacket);
	}
}
//=============================================================================
function parseGetFile(client, strPacket, curIdx) {
	var findIdx, filename;

	findIdx = strPacket.indexOf(',', curIdx);
	filename = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	moduleFs.readFile(__dirname + '/' + filename, function(err, data) {
		var packet = '$sendfile:';
		var filedata;
		if (undefined != data) {
			filedata = data.toString();
		} else {
			filedata = '<?xml version="1.0" encoding="utf-8"?>';
		}
		packet += (filename + ',');
		packet += (filedata.length + ',');
		packet += (filedata);
		packet += '$';
		client.write(packet);
	});
}
//=============================================================================
function parseSendFile(client, strPacket, curIdx) {
	var findIdx, filename, filesize, filedata;

	findIdx = strPacket.indexOf(',', curIdx);
	filename = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	findIdx = strPacket.indexOf(',', curIdx);
	filesize = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	findIdx = strPacket.indexOf('$', curIdx);
	filedata = strPacket.substr(curIdx, findIdx-curIdx);
	curIdx = findIdx + 1;

	moduleFs.exists(__dirname + '/' + filename, function (exists) {
		if (true == exists) {
			var curDate = new Date();
			var date, num;

			date = curDate.getFullYear();
			num = curDate.getMonth() + 1;
			date += ((10 > num) ? ('0' + num) : num);
			num = curDate.getDate();
			date += ((10 > num) ? ('0' + num) : num);
			date += '-';

			num = curDate.getHours();
			date += ((10 > num) ? ('0' + num) : num);
			num = curDate.getMinutes();
			date += ((10 > num) ? ('0' + num) : num);
			num = curDate.getSeconds();
			date += ((10 > num) ? ('0' + num) : num);

			var newPath = __dirname + '/' + filename + '.' + date;
			moduleFs.rename(__dirname + '/' + filename, newPath, function(err) {
				moduleFs.writeFile(__dirname + '/' + filename, filedata, function(err) {
				});
			});
		} else {
			moduleFs.writeFile(__dirname + '/' + filename, filedata, function(err) {
			});
		}
	});

	// throw out controlServer
	for (var i=0; i<controlClients.length; i++) {
		controlClients[i].write(strPacket);
	}
}
//=============================================================================
function parseOption(strPacket) {
	// $option:[,],[,],$
	
	// throw out controlServer
	for (var i=0; i<controlClients.length; i++) {
		controlClients[i].write(strPacket);
	}
}
//=============================================================================
function parseTemperature(strPacket) {
	// qt-remote send temperature
	for (var i=0; i<remoteClients.length; i++) {
		//console.log('remoteServer send temperature :', remoteClients[i]._addr);
		remoteClients[i].write(strPacket);
	}
	// web-client send layout
	for (var i=0; i<httpClients.length; i++) {
		//console.log('httpServer send temperature :', httpClients[i]._addr);
		httpClients[i].emit('temperature', strPacket);
	}
}
//=============================================================================
function makePacketLayout() {
	var packet = '$layout:';

	packet += (layoutData.cx + ',');
	packet += (layoutData.cy + ',');
	packet += (layoutData.row + ',');
	packet += (layoutData.column + ',');
	for (var i=0; i<layoutData.Button.length; i++) {
		packet += ('[');
		packet += (layoutData.Button[i].cid + ',');
		packet += (layoutData.Button[i].bid + ',');
		packet += (layoutData.Button[i].rid + ',');
		packet += (layoutData.Button[i].name + ',');
		packet += (layoutData.Button[i].notUsed + ',');
		packet += (layoutData.Button[i].pos + ',');
		packet += (layoutData.Button[i].xycxcy + ',');
		packet += (layoutData.Button[i].rtype + ',');
		packet += (layoutData.Button[i].status + ',');
		packet += ('],');
	}
	packet += '$';
	
	return packet;
}
//=============================================================================
